package com.zoro.barn.commons.db.autopage;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author zhaoxingwu
 */
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
public class BarnPagePlugin implements Interceptor {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final Set<String> MAPPER_ID = new HashSet<>();
    static boolean CAN_ADD = true;

    public BarnPagePlugin() {
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        boolean isInfoEnable = logger.isInfoEnabled();

        Object[] args = invocation.getArgs();
        Executor executor = (Executor) invocation.getTarget();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler<?> resultHandler = (ResultHandler<?>) args[3];
        CacheKey cacheKey;
        BoundSql boundSql;
        if (args.length == 6) {
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        } else {
            boundSql = mappedStatement.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
        }

        String id = mappedStatement.getId();
        if (!MAPPER_ID.contains(id)) {
            if (isInfoEnable) {
                logger.info("{} don't need page", id);
            }
            return invocation.proceed();
        }

        int current = PageParamFilter.currentPage();
        int pageSize = PageParamFilter.pageSize();
        if (current <= 0 || pageSize < 0) {
            if (isInfoEnable) {
                logger.info("page size ({}) or current page({}) is o, don't need page", pageSize, current);
            }
            return invocation.proceed();
        }

        Configuration configuration = mappedStatement.getConfiguration();

        // 查询总条数
        MappedStatement newMs = PagerUtils.makeCountMappedStatement(mappedStatement, id + "_count");
        BoundSql countBs = PagerUtils.makeCountSql(boundSql, configuration);
        CacheKey countCacheKey = executor.createCacheKey(newMs, parameter, RowBounds.DEFAULT, countBs);
        List<Long> countList = executor.query(newMs, parameter, RowBounds.DEFAULT, resultHandler, countCacheKey, countBs);
        if (countList == null || countList.size() == 0 || countList.get(0) == null || countList.get(0).equals(0L)) {
            // 没有数据，不用查了
            if (isInfoEnable) {
                logger.info("there is no data to return");
            }
            return Collections.emptyList();
        }
        // 修正数据 ， 防止可能出色入参超出最大行数
        long count = countList.get(0);
        int totalPage = (int) Math.ceil((double) count / pageSize);
        int offset = (current - 1) * pageSize;
        if (offset > count) {
            current = totalPage;
        }
        offset = (current - 1) * pageSize;

        // 查询结果集
        BoundSql newBs = PagerUtils.makePageSql(boundSql, pageSize, offset, configuration);
        List<?> result = executor.query(mappedStatement, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, newBs);
        return new PageList<Object>(result, current, count);
    }


    public static void addNeedPageId(String id) {
        if (!CAN_ADD || id == null || id.isBlank()) {
            return;
        }
        MAPPER_ID.add(id);
    }

    static void setCanAdd() {
        CAN_ADD = false;
    }

}
